2. Step: Sentiment Analysis
2.1 Normalize Scores
We use a customized min/max normalization method here, to make sure all values of the Afinn lexicon are scaled with respect to Afinn’s minimum label (-5) and maximum label (+5). The computed sentiment scores will be normalized between -1 and +1.
# ===== DATA NORMALIZATION =====
# Normalize data via minimun/maximum normalization, either by scaling values from 0 to 1 or -1 to 1
#
# Arg:
# x: input values (e.g. column of data frame)
#
# Returns:
# normalized data
# min/max normalization from -1 to 1, relative to data frame results
normalize <- function(x, na.rm = TRUE){
return(2* ((x - min(x)) / (max(x)-min(x)))-1)}
# min/max normalization for afinn data, wrt to afinn scoring (-5, +5)
normalize_afinn <- function(x, na.rm = TRUE){
return(2* ((x - (-5)) / (5-(-5)))-1)}
2.2 Compute Sentiment Scores
In this step sentiment scores for the different lexicons (Afinn, LSD and Vader) are calculated by using the texstat_valence (Afinn) and textstat_polarity (LSD) functions of the quanteda.sentiment library and the vader_compound score of the vader library. Originally two versions of normalization were tested. For the final sentiment scoring we decided on the normalize_afinn function.
# ===== SENTIMENT SCORES =====
# Calculate sentiment scores for different lexicons and input data frames
#
# Arg:
# data: input data frame
# lexicons: names of lexicons that should be used for sentiment scoring
# normalize: "relative" if normalization is handled relative to output, i.e. output column of afinn is being scaled via min/max normalization
# normalize: "afinn" if normalization is handled by taking -5 as new minimum and +5 as new maximum and everything else is scaled between
# get_tokens: if TRUE final data frame consists of single tokens with an associated sentiment score
# else final data frame consists of an associated sentiment score per input text instead (only used to calculate TOP-N words)
#
# Returns:
# data frame with (normalized) sentiment sores for chosen lexicons
get_sentiment <- function(df, lexicons, normalize, get_tokens){
# if we want data frame with single tokens
if(get_tokens==TRUE){
df <- df %>%
# get list of tokens as new col in data frame
unnest_tokens(token,text)
# assign the new col as input for sentiment lexicons
tok = df$token
# for each lexicon, get sentiment scores and save scores in new column of data frame
for(lex in lexicons){
if(lex == "afinn"){
df$afinn <- round(textstat_valence(tok, afinn, normalize="dictionary")$sentiment,3)}
if(lex == "lsd"){
df$lsd<- round(textstat_polarity(tok, lsd, fun=sent_relpropdiff)$sentiment,3)}
if(lex == "vader"){
df$vader <- round(vader_df(tok)$compound,3)}
}
# if we don't want to analyze single tokens but input text as "whole"
}else{
tok = tokens(df$text)
for(lex in lexicons){
if(lex == "afinn"){
df$afinn <- round(textstat_valence(tok, afinn, normalize="dictionary")$sentiment,3)}
if(lex == "lsd"){
df$lsd<- round(textstat_polarity(tok, lsd, fun=sent_relpropdiff)$sentiment,3)}
if(lex == "vader"){
df$vader <- round(vader_df(df$text)$compound,3)}
}
}
# normalize sentiment scores if TRUE, VADER and LSD scores are already normalized within functions above
if(normalize=="afinn"){
df$afinn <- round(normalize_afinn(df$afinn), 3)
}else{
df$afinn <- round(normalize(df$afinn), 3)}
# set sentiment scores to 0 if NA
df[is.na(df)] <- 0
return(df)
}
# apply get_sentiment function to corpus data
### BASELINE
reviews_sentiment_norm1 <- get_sentiment(reviews, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
twitter_sentiment_norm1 <- get_sentiment(twitter, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
parlvote_sentiment_norm1 <- get_sentiment(parlvote, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
amazon_sentiment_norm1 <- get_sentiment(amazon, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=FALSE)
finance_sentiment_norm1 <- get_sentiment(finance, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=FALSE)
parlvote_corr_norm1 <- get_sentiment(parlvote_corr, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=FALSE)
### CONFIG2
reviews_config2_sentiment_norm1 <- get_sentiment(reviews_config2, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
twitter_config2_sentiment_norm1 <- get_sentiment(twitter_config2, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
parlvote_config2_sentiment_norm1 <- get_sentiment(parlvote_config2, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=FALSE)
amazon_config2_sentiment_norm1 <- get_sentiment(amazon_config2, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=FALSE)
finance_config2_sentiment_norm1 <- get_sentiment(finance_config2, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=FALSE)
4. Step: Compute Coverage
To compute the coverage of each sentiment lexicon and for each corpus, we consider all texts or tokens receiving a score of 0 to be not-recognized by the according lexicon, i.e. the word does not exist in the lexicon or the whole text did not contain a word which is contained in the lexicon. We compute the coverage per token and the coverage per text. In case of the coverage per token, we consider 2 configurations: 1) a baseline configuration with a simple token-count to get the coverage score, 2) a configuration of the tokens with a prior stopword removal.
# get sentiment for each token in corpus
reviews_tok_sent <- get_sentiment(reviews, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=TRUE)
twitter_tok_sent <- get_sentiment(twitter, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=TRUE)
parlvote_tok_sent <- get_sentiment(parlvote, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=TRUE)
amazon_tok_sent <- get_sentiment(amazon, c("afinn","lsd", "vader"), normalize="afinn", get_tokens=TRUE)
finance_tok_sent <- get_sentiment(finance, c("afinn", "lsd", "vader"), normalize="afinn", get_tokens=TRUE)
# convert values to discrete format
reviews_tok_discrete <- get_discrete(reviews_tok_sent, c("afinn","vader","lsd"))
twitter_tok_discrete <- get_discrete(twitter_tok_sent, c("afinn","vader","lsd"))
parlvote_tok_discrete <- get_discrete(parlvote_tok_sent, c("afinn","vader","lsd"))
amazon_tok_discrete <- get_discrete(amazon_tok_sent, c("afinn","vader","lsd"))
finance_tok_discrete <- get_discrete(finance_tok_sent, c("afinn","vader","lsd"))
# ===== COVERAGE PER TOKEN =====
# Coverage per token: if sentiment score is not zero, count as "covered", get percentage of covered tokens
reviews_tok_coverage <- `rownames<-`(data.frame(t(round(colSums(reviews_tok_sent[c("afinn","lsd","vader")] != 0)/colSums(reviews_tok_sent != 0)[[1]]*100,2))), "reviews tokens coverage (%)")
twitter_tok_coverage <- `rownames<-`(data.frame(t(round(colSums(twitter_tok_sent[c("afinn","lsd","vader")] != 0)/colSums(twitter_tok_sent != 0)[[1]]*100,2))), "twitter tokens coverage (%)")
parlvote_tok_coverage <- `rownames<-`(data.frame(t(round(colSums(parlvote_tok_sent[c("afinn","lsd","vader")] != 0)/colSums(parlvote_tok_sent != 0)[[1]]*100,2))), "parlvote tokens coverage (%)")
amazon_tok_coverage <- `rownames<-`(data.frame(t(round(colSums(amazon_tok_sent[c("afinn","lsd","vader")] != 0)/colSums(amazon_tok_sent != 0)[[1]]*100,2))), "amazon tokens coverage (%)")
finance_tok_coverage <- `rownames<-`(data.frame(t(round(colSums(finance_tok_sent[c("afinn","lsd","vader")] != 0)/colSums(finance_tok_sent != 0)[[1]]*100,2))), "finance tokens coverage (%)")
# Coverage per token: with stopword removal
# remove stopwords from each data frame
reviews_tok.stopwords <- reviews_tok_sent %>%
anti_join(stop_words, by= c("token" = "word") ) %>%
ungroup()
twitter_tok.stopwords <- twitter_tok_sent %>%
anti_join(stop_words, by= c("token" = "word") ) %>%
ungroup()
parlvote_tok.stopwords <- parlvote_tok_sent %>%
anti_join(stop_words, by= c("token" = "word") ) %>%
ungroup()
amazon_tok.stopwords <- amazon_tok_sent %>%
anti_join(stop_words, by= c("token" = "word") ) %>%
ungroup()
finance_tok.stopwords <- finance_tok_sent %>%
anti_join(stop_words, by= c("token" = "word") ) %>%
ungroup()
# same as above: if sentiment score is not zero, count as "covered", get percentage of covered tokens
reviews_tok_coverage.stopwords <- `rownames<-`(data.frame(t(round(colSums(reviews_tok.stopwords[c("afinn","lsd","vader")] != 0)/colSums(reviews_tok.stopwords != 0)[[1]]*100,2))), "reviews tokens coverage (%) - stopwords")
twitter_tok_coverage.stopwords <- `rownames<-`(data.frame(t(round(colSums(twitter_tok.stopwords[c("afinn","lsd","vader")] != 0)/colSums(twitter_tok.stopwords != 0)[[1]]*100,2))), "twitter tokens coverage (%) - stopwords")
parlvote_tok_coverage.stopwords <- `rownames<-`(data.frame(t(round(colSums(parlvote_tok.stopwords[c("afinn","lsd","vader")] != 0)/colSums(parlvote_tok.stopwords != 0)[[1]]*100,2))), "parlvote tokens coverage (%) - stopwords")
amazon_tok_coverage.stopwords <- `rownames<-`(data.frame(t(round(colSums(amazon_tok.stopwords[c("afinn","lsd","vader")] != 0)/colSums(amazon_tok.stopwords != 0)[[1]]*100,2))), "amazon tokens coverage (%) - stopwords")
finance_tok_coverage.stopwords <- `rownames<-`(data.frame(t(round(colSums(finance_tok.stopwords[c("afinn","lsd","vader")] != 0)/colSums(finance_tok.stopwords != 0)[[1]]*100,2))), "finance tokens coverage (%) - stopwords")
# ===== COVERAGE PER TEXT =====
# Coverage per text: if sentiment score is not zero, count as "covered", get percentage of covered text instances
reviews_coverage <- `rownames<-`(data.frame(t(colSums(reviews_sentiment_norm1[c("afinn","lsd","vader")] != 0)/10)), "reviews text coverage (%)")
twitter_coverage <- `rownames<-`(data.frame(t(colSums(twitter_sentiment_norm1[c("afinn","lsd","vader")] != 0)/10)), "twitter text coverage (%)")
parlvote_coverage <- `rownames<-`(data.frame(t(colSums(parlvote_sentiment_norm1[c("afinn","lsd","vader")] != 0)/10)), "parlvote text coverage (%)")
amazon_coverage <- `rownames<-`(data.frame(t(colSums(amazon_sentiment_norm1[c("afinn","lsd","vader")] != 0)/10)), "amazon text coverage (%)")
finance_coverage <- `rownames<-`(data.frame(t(colSums(finance_sentiment_norm1[c("afinn","lsd","vader")] != 0)/10)), "finance text coverage (%)")
# save data to data frame
coverage <- rbind(reviews_tok_coverage,reviews_tok_coverage.stopwords,reviews_coverage, twitter_tok_coverage,twitter_tok_coverage.stopwords,parlvote_tok_coverage,twitter_coverage,parlvote_tok_coverage.stopwords, parlvote_coverage, amazon_tok_coverage,amazon_tok_coverage.stopwords, amazon_coverage, finance_tok_coverage,finance_tok_coverage.stopwords, finance_coverage)
Display Coverage Results
5. Step: Plot Data
5.1 Plot Sentiment Scores
# ===== PLOT SENTIMENT SCORES =====
# create data frame with sentiment scores as variable of first 100 instances of corpus
reviews_df <- melt(head(reviews_sentiment_norm1,100)[,c('id','afinn','lsd','vader')],id.vars = 1)
twitter_df <- melt(head(twitter_sentiment_norm1,100)[,c('id','afinn','lsd','vader')],id.vars = 1)
parlvote_df <- melt(head(parlvote_sentiment_norm1,100)[,c('id','afinn','lsd','vader')],id.vars = 1)
amazon_df <- melt(head(amazon_sentiment_norm1,100)[,c('id','afinn','lsd','vader')],id.vars = 1)
finance_df <- melt(head(finance_sentiment_norm1,100)[,c('id','afinn','lsd','vader')],id.vars = 1)
# create plots for each corpus
reviews_plot <- ggplot(reviews_df,aes(x = id,y = value)) +
geom_bar(aes(fill = variable),stat = "identity",position = "dodge") +
facet_wrap(~ variable, ncol = 1, scales="free_y")+
xlab("text id")+ ylab("sentiment score")+
ggtitle("Reviews Sentiment")
twitter_plot <- ggplot(twitter_df,aes(x = id,y = value)) +
geom_bar(aes(fill = variable),stat = "identity",position = "dodge") +
xlab("text id")+ ylab("sentiment score")+
ggtitle("Twitter Sentiment")
parlvote_plot <- ggplot(parlvote_df,aes(x = id,y = value)) +
geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
#facet_wrap(~ variable, ncol = 1, scales="free_y")+
xlab("text id")+ ylab("sentiment score")+
ggtitle("ParlVote Sentiment")
amazon_plot <- ggplot(amazon_df,aes(x = id,y = value)) +
geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
#facet_wrap(~ variable, ncol = 1, scales="free_y")+
xlab("text id")+ ylab("sentiment score")+
ggtitle("Amazon Sentiment")
finance_plot <- ggplot(finance_df,aes(x = id,y = value)) +
geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
#facet_wrap(~ variable, ncol = 1, scales="free_y")+
xlab("text id")+ ylab("sentiment score")+
ggtitle("Finance Sentiment")
reviews_plot.line <- ggplot(reviews_df,aes(x = id, y = value, group=variable)) +
geom_line(aes(colour=variable), size=0.4)+
ylim(-1,1) +
ggtitle("Reviews Sentiment Scores")
# show plots
reviews_plot

reviews_plot.line

twitter_plot

parlvote_plot

amazon_plot

finance_plot

5.2 Plot Important Words
To plot important words per sentiment we use the sentiment scores for each token (that we calculated for the coverage part) and convert continuous scores to a discrete format.
# load stopwords
data(stop_words)
In the next step, word counts for each discrete (sentiment) variable (“positive”, “negative”, “neutral”) are retrieved for each lexicon.
# get word counts per discrete variable ("positive", "negative", "neutral", i.e. 1,-1,0)
reviews_afinn.word_counts <- reviews_tok_discrete %>%
count(token, afinn, sort = TRUE) %>%
anti_join(stop_words, by= c("token" = "word") ) %>%
ungroup()
reviews_lsd.word_counts <- reviews_tok_discrete %>%
count(token, lsd, sort = TRUE) %>%
anti_join(stop_words, by= c("token" = "word") ) %>%
ungroup()
reviews_vader.word_counts <- reviews_tok_discrete %>%
count(token, vader, sort = TRUE) %>%
anti_join(stop_words, by= c("token" = "word") ) %>%
ungroup()
Now we can plot the top 20 words for each sentiment.
# plot top n words for each sentiment and for each lexicon, n = 20
topn_reviews_afinn.plot <- reviews_afinn.word_counts %>%
group_by(afinn) %>%
slice(1:20) %>%
ggplot(aes(reorder(token, n), n, fill = afinn)) +
geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
facet_wrap(~afinn, scales = "free_y") +
labs(y = "contribution to sentiment", x = NULL) +
coord_flip()+
ggtitle("Reviews: Top 20 Words per Sentiment (Afinn)")
topn_reviews_lsd.plot <- reviews_lsd.word_counts %>%
group_by(lsd) %>%
slice(1:20) %>%
ggplot(aes(reorder(token, n), n, fill = lsd)) +
geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
facet_wrap(~lsd, scales = "free_y") +
labs(y = "contribution to sentiment", x = NULL) +
coord_flip()+
ggtitle("Reviews: Top 20 Words per Sentiment (LSD)")
topn_reviews_vader.plot <- reviews_vader.word_counts %>%
group_by(vader) %>%
slice(1:20) %>%
ggplot(aes(reorder(token, n), n, fill = vader)) +
geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
facet_wrap(~vader, scales = "free_y") +
labs(y = "contribution to sentiment", x = NULL) +
coord_flip()+
ggtitle("Reviews: Top 20 Words per Sentiment (VADER)")
topn_reviews_afinn.plot

topn_reviews_lsd.plot

topn_reviews_vader.plot

LS0tCnRpdGxlOiAiU2VudGltZW50IFRvb2xzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyMgUmVxdWlyZW1lbnRzCmBgYHtyfQojIGxvYWQgcmVxdWlyZWQgbGlicmFyaWVzCgpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KHJlYWR0ZXh0KQojbGlicmFyeShjb3JwdXMpCiNsaWJyYXJ5KHRpZHl2ZXJzZSkKI2xpYnJhcnkoc3RyaW5ncikKI2xpYnJhcnkodGlkeXRleHQpCiNsaWJyYXJ5KGhhcnJ5cG90dGVyKQojbGlicmFyeShkcGx5cikKbGlicmFyeShxdWFudGVkYS5zZW50aW1lbnQpCmxpYnJhcnkodmFkZXIpCiNsaWJyYXJ5KGNhcmV0KQojbGlicmFyeShyZXNoYXBlMikKCgojcmVxdWlyZShxdWFudGVkYSkKI3JlcXVpcmUocXVhbnRlZGEuY29ycG9yYSkKI3JlcXVpcmUocXVhbnRlZGEuc2VudGltZW50KQpgYGAKCiMjIyAxLiBTdGVwOiBMb2FkIENvcnB1cyBEYXRhICYgU2VudGltZW50IExleGljb25zCmBgYHtyfQojID09PT09IERBVEFTRVRTID09PT09CiMgbG9hZCBCQVNFTElORSBkYXRhc2V0cwpyZXZpZXdzIDwtIHJlYWRSRFMoZmlsZT0iZGF0YXNldHMvYmFzZWxpbmUvYm9va3NfY29uZmlnX2Jhc2UucmRzIikKdHdpdHRlciA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL2Jhc2VsaW5lL3R3aXR0ZXJfY29uZmlnX2Jhc2UucmRzIikKcGFybHZvdGUgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9iYXNlbGluZS9wYXJsX2NvbmZpZ19iYXNlLnJkcyIpCmFtYXpvbiA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL2Jhc2VsaW5lL2FtYV9jb25maWdfYmFzZS5yZHMiKQpmaW5hbmNlIDwtIHJlYWRSRFMoZmlsZT0iZGF0YXNldHMvYmFzZWxpbmUvZmluX2NvbmZpZ19iYXNlLnJkcyIpCnBhcmx2b3RlX2NvcnIgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9jb3JyX3BhcnQucmRzIikgCgojIGxvYWQgcHJlcHJvY2Vzc2VkIGRhdGFzZXRzCnJldmlld3NfY29uZmlnMiA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL2NvbmZpZ18yL2Jvb2tfY29uZmlnXzIucmRzIikKdHdpdHRlcl9jb25maWcyIDwtIHJlYWRSRFMoZmlsZT0iZGF0YXNldHMvY29uZmlnXzIvdHdpdHRlcl9jb25maWdfMi5yZHMiKQpwYXJsdm90ZV9jb25maWcyIDwtIHJlYWRSRFMoZmlsZT0iZGF0YXNldHMvY29uZmlnXzIvcGFybF9jb25maWdfMi5yZHMiKQphbWF6b25fY29uZmlnMiA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL2NvbmZpZ18yL2FtYV9jb25maWdfMi5yZHMiKQpmaW5hbmNlX2NvbmZpZzIgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9jb25maWdfMi9maW5fY29uZmlnXzIucmRzIikKCiMgPT09PT0gU0VOVElNRU5UIExFWElDT05TID09PT09PQojIGxvYWQgbGV4aWNvbnMKYWZpbm4gPC0gZGF0YV9kaWN0aW9uYXJ5X0FGSU5OCmxzZCA8LSBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNQpgYGAKCiMjIyMjIExvb2sgYXQgRXhhbXBsZSBDb3JwdXM6IFBhcmxWb3RlCkVhY2ggY29ycHVzIGRhdGEgZnJhbWUgY29uc2lzdHMgb2Y6ICAKLSBJRDogb3JpZ2luYWwgZG9jdW1lbnQgaWQgIAotIFRleHQ6IHdpbGwgYmUgdGhlIGlucHV0IHRvIHRoZSBzZW50aW1lbnQgYW5hbHlzaXMgIAotIFJhdGluZzogd2lsbCBiZSB0aGUgZ29sZCBzdGFuZGFyZCB0byBldmFsdWF0ZSBsZXhpY29uIHBlcmZvcm1hbmNlICAgIApgYGB7cn0KcGFybHZvdGUKYGBgCgojIyMgMi4gU3RlcDogU2VudGltZW50IEFuYWx5c2lzCiMjIyMgMi4xIE5vcm1hbGl6ZSBTY29yZXMKV2UgdXNlIGEgY3VzdG9taXplZCBtaW4vbWF4IG5vcm1hbGl6YXRpb24gbWV0aG9kIGhlcmUsIHRvIG1ha2Ugc3VyZSBhbGwgdmFsdWVzIG9mIHRoZSBBZmlubiBsZXhpY29uIGFyZSBzY2FsZWQgd2l0aCByZXNwZWN0IHRvIEFmaW5uJ3MgbWluaW11bSBsYWJlbCAoLTUpIGFuZCBtYXhpbXVtIGxhYmVsICgrNSkuIFRoZSBjb21wdXRlZCBzZW50aW1lbnQgc2NvcmVzIHdpbGwgYmUgbm9ybWFsaXplZCBiZXR3ZWVuIC0xIGFuZCArMS4gCmBgYHtyfQojID09PT09IERBVEEgTk9STUFMSVpBVElPTiA9PT09PQoKIyBOb3JtYWxpemUgZGF0YSB2aWEgbWluaW11bi9tYXhpbXVtIG5vcm1hbGl6YXRpb24sIGVpdGhlciBieSBzY2FsaW5nIHZhbHVlcyBmcm9tIDAgdG8gMSBvciAtMSB0byAxCiMgCiMgQXJnOgojICAgeDogaW5wdXQgdmFsdWVzIChlLmcuIGNvbHVtbiBvZiBkYXRhIGZyYW1lKQojICAgCiMgUmV0dXJuczogCiMgICBub3JtYWxpemVkIGRhdGEKCiMgbWluL21heCBub3JtYWxpemF0aW9uIGZyb20gLTEgdG8gMSwgcmVsYXRpdmUgdG8gZGF0YSBmcmFtZSByZXN1bHRzCm5vcm1hbGl6ZSA8LSBmdW5jdGlvbih4LCBuYS5ybSA9IFRSVUUpewogIHJldHVybigyKiAoKHggLSBtaW4oeCkpIC8gKG1heCh4KS1taW4oeCkpKS0xKX0KCiMgbWluL21heCBub3JtYWxpemF0aW9uIGZvciBhZmlubiBkYXRhLCB3cnQgdG8gYWZpbm4gc2NvcmluZyAoLTUsICs1KSAKbm9ybWFsaXplX2FmaW5uIDwtIGZ1bmN0aW9uKHgsIG5hLnJtID0gVFJVRSl7CiAgcmV0dXJuKDIqICgoeCAtICgtNSkpIC8gKDUtKC01KSkpLTEpfQpgYGAKCiMjIyMgMi4yIENvbXB1dGUgU2VudGltZW50IFNjb3JlcwpJbiB0aGlzIHN0ZXAgc2VudGltZW50IHNjb3JlcyBmb3IgdGhlIGRpZmZlcmVudCBsZXhpY29ucyAoQWZpbm4sIExTRCBhbmQgVmFkZXIpIGFyZSBjYWxjdWxhdGVkIGJ5IHVzaW5nIHRoZSBgdGV4c3RhdF92YWxlbmNlYCAoQWZpbm4pIGFuZCBgdGV4dHN0YXRfcG9sYXJpdHlgIChMU0QpIGZ1bmN0aW9ucyBvZiB0aGUgKnF1YW50ZWRhLnNlbnRpbWVudCogbGlicmFyeSBhbmQgdGhlIGB2YWRlcl9jb21wb3VuZGAgc2NvcmUgb2YgdGhlICp2YWRlciogbGlicmFyeS4gT3JpZ2luYWxseSB0d28gdmVyc2lvbnMgb2Ygbm9ybWFsaXphdGlvbiB3ZXJlIHRlc3RlZC4gRm9yIHRoZSBmaW5hbCBzZW50aW1lbnQgc2NvcmluZyB3ZSBkZWNpZGVkIG9uIHRoZSBgbm9ybWFsaXplX2FmaW5uYCBmdW5jdGlvbi4gCmBgYHtyfQojID09PT09IFNFTlRJTUVOVCBTQ09SRVMgPT09PT0KCiMgQ2FsY3VsYXRlIHNlbnRpbWVudCBzY29yZXMgZm9yIGRpZmZlcmVudCBsZXhpY29ucyBhbmQgaW5wdXQgZGF0YSBmcmFtZXMKIyAKIyBBcmc6CiMgIGRhdGE6IGlucHV0IGRhdGEgZnJhbWUKIyAgbGV4aWNvbnM6IG5hbWVzIG9mIGxleGljb25zIHRoYXQgc2hvdWxkIGJlIHVzZWQgZm9yIHNlbnRpbWVudCBzY29yaW5nCiMgIG5vcm1hbGl6ZTogInJlbGF0aXZlIiBpZiBub3JtYWxpemF0aW9uIGlzIGhhbmRsZWQgcmVsYXRpdmUgdG8gb3V0cHV0LCBpLmUuIG91dHB1dCBjb2x1bW4gb2YgYWZpbm4gaXMgYmVpbmcgc2NhbGVkIHZpYSBtaW4vbWF4IG5vcm1hbGl6YXRpb24KIyAgbm9ybWFsaXplOiAiYWZpbm4iIGlmIG5vcm1hbGl6YXRpb24gaXMgaGFuZGxlZCBieSB0YWtpbmcgLTUgYXMgbmV3IG1pbmltdW0gYW5kICs1IGFzIG5ldyBtYXhpbXVtIGFuZCBldmVyeXRoaW5nIGVsc2UgaXMgc2NhbGVkIGJldHdlZW4KIyAgZ2V0X3Rva2VuczogaWYgVFJVRSBmaW5hbCBkYXRhIGZyYW1lIGNvbnNpc3RzIG9mIHNpbmdsZSB0b2tlbnMgd2l0aCBhbiBhc3NvY2lhdGVkIHNlbnRpbWVudCBzY29yZQojICAgICAgICAgICAgICBlbHNlIGZpbmFsIGRhdGEgZnJhbWUgY29uc2lzdHMgb2YgYW4gYXNzb2NpYXRlZCBzZW50aW1lbnQgc2NvcmUgcGVyIGlucHV0IHRleHQgaW5zdGVhZCAob25seSB1c2VkIHRvIGNhbGN1bGF0ZSBUT1AtTiB3b3JkcykKIyAgIAojIFJldHVybnM6IAojICBkYXRhIGZyYW1lIHdpdGggKG5vcm1hbGl6ZWQpIHNlbnRpbWVudCBzb3JlcyBmb3IgY2hvc2VuIGxleGljb25zCgpnZXRfc2VudGltZW50IDwtIGZ1bmN0aW9uKGRmLCBsZXhpY29ucywgbm9ybWFsaXplLCBnZXRfdG9rZW5zKXsKICAgCiAgICMgaWYgd2Ugd2FudCBkYXRhIGZyYW1lIHdpdGggc2luZ2xlIHRva2VucyAKICAgaWYoZ2V0X3Rva2Vucz09VFJVRSl7CiAgICAgIGRmIDwtIGRmICU+JQogICAgICAgICAjIGdldCBsaXN0IG9mIHRva2VucyBhcyBuZXcgY29sIGluIGRhdGEgZnJhbWUKICAgICAgICAgdW5uZXN0X3Rva2Vucyh0b2tlbix0ZXh0KQogICAgICAKICAgICAgIyBhc3NpZ24gdGhlIG5ldyBjb2wgYXMgaW5wdXQgZm9yIHNlbnRpbWVudCBsZXhpY29ucwogICAgICB0b2sgPSBkZiR0b2tlbgogICAgIAogICAgICAjIGZvciBlYWNoIGxleGljb24sIGdldCBzZW50aW1lbnQgc2NvcmVzIGFuZCBzYXZlIHNjb3JlcyBpbiBuZXcgY29sdW1uIG9mIGRhdGEgZnJhbWUKICAgICAgZm9yKGxleCBpbiBsZXhpY29ucyl7CiAgICAgICAgIAogICAgICAgICBpZihsZXggPT0gImFmaW5uIil7CiAgICAgICAgICAgIGRmJGFmaW5uIDwtIHJvdW5kKHRleHRzdGF0X3ZhbGVuY2UodG9rLCBhZmlubiwgbm9ybWFsaXplPSJkaWN0aW9uYXJ5Iikkc2VudGltZW50LDMpfQogICAgICAgICBpZihsZXggPT0gImxzZCIpewogICAgICAgICAgICBkZiRsc2Q8LSByb3VuZCh0ZXh0c3RhdF9wb2xhcml0eSh0b2ssIGxzZCwgZnVuPXNlbnRfcmVscHJvcGRpZmYpJHNlbnRpbWVudCwzKX0KICAgICAKICAgICAgICAgaWYobGV4ID09ICJ2YWRlciIpewogICAgICAgICAgICBkZiR2YWRlciA8LSByb3VuZCh2YWRlcl9kZih0b2spJGNvbXBvdW5kLDMpfQogICB9CiAgICAKICAgIyBpZiB3ZSBkb24ndCB3YW50IHRvIGFuYWx5emUgc2luZ2xlIHRva2VucyBidXQgaW5wdXQgdGV4dCBhcyAid2hvbGUiCiAgIH1lbHNlewogICAgICAKICAgICAgdG9rID0gdG9rZW5zKGRmJHRleHQpCiAgICAgIAogICAgICBmb3IobGV4IGluIGxleGljb25zKXsKICAgICAgICAgaWYobGV4ID09ICJhZmlubiIpewogICAgICAgICAgICBkZiRhZmlubiA8LSByb3VuZCh0ZXh0c3RhdF92YWxlbmNlKHRvaywgYWZpbm4sIG5vcm1hbGl6ZT0iZGljdGlvbmFyeSIpJHNlbnRpbWVudCwzKX0KICAgICAgICAgCiAgICAgICAgIGlmKGxleCA9PSAibHNkIil7CiAgICAgICAgICAgIGRmJGxzZDwtIHJvdW5kKHRleHRzdGF0X3BvbGFyaXR5KHRvaywgbHNkLCBmdW49c2VudF9yZWxwcm9wZGlmZikkc2VudGltZW50LDMpfQogICAgICAgICAKICAgICAgICAgaWYobGV4ID09ICJ2YWRlciIpewogICAgICAgICAgICBkZiR2YWRlciA8LSByb3VuZCh2YWRlcl9kZihkZiR0ZXh0KSRjb21wb3VuZCwzKX0KICAgICB9CiAgIH0gIAogICAKICAgIyBub3JtYWxpemUgc2VudGltZW50IHNjb3JlcyBpZiBUUlVFLCBWQURFUiBhbmQgTFNEIHNjb3JlcyBhcmUgYWxyZWFkeSBub3JtYWxpemVkIHdpdGhpbiBmdW5jdGlvbnMgYWJvdmUKCiAgIGlmKG5vcm1hbGl6ZT09ImFmaW5uIil7CiAgICAgIGRmJGFmaW5uIDwtIHJvdW5kKG5vcm1hbGl6ZV9hZmlubihkZiRhZmlubiksIDMpCiAgIH1lbHNlewogICAgICBkZiRhZmlubiA8LSByb3VuZChub3JtYWxpemUoZGYkYWZpbm4pLCAzKX0KICAgCiAgICMgc2V0IHNlbnRpbWVudCBzY29yZXMgdG8gMCBpZiBOQQogICBkZltpcy5uYShkZildIDwtIDAKICAgCiAgIHJldHVybihkZikKfQoKIyBhcHBseSBnZXRfc2VudGltZW50IGZ1bmN0aW9uIHRvIGNvcnB1cyBkYXRhIAojIyMgQkFTRUxJTkUgIApyZXZpZXdzX3NlbnRpbWVudF9ub3JtMSA8LSBnZXRfc2VudGltZW50KHJldmlld3MsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIiksIG5vcm1hbGl6ZT0iYWZpbm4iLCBnZXRfdG9rZW5zPUZBTFNFKQp0d2l0dGVyX3NlbnRpbWVudF9ub3JtMSA8LSBnZXRfc2VudGltZW50KHR3aXR0ZXIsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIiksIG5vcm1hbGl6ZT0iYWZpbm4iLCBnZXRfdG9rZW5zPUZBTFNFKQpwYXJsdm90ZV9zZW50aW1lbnRfbm9ybTEgPC0gZ2V0X3NlbnRpbWVudChwYXJsdm90ZSwgYygiYWZpbm4iLCJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCmFtYXpvbl9zZW50aW1lbnRfbm9ybTEgPC0gZ2V0X3NlbnRpbWVudChhbWF6b24sIGMoImFmaW5uIiwgImxzZCIsICJ2YWRlciIpLCBub3JtYWxpemU9ImFmaW5uIiwgZ2V0X3Rva2Vucz1GQUxTRSkKZmluYW5jZV9zZW50aW1lbnRfbm9ybTEgPC0gZ2V0X3NlbnRpbWVudChmaW5hbmNlLCBjKCJhZmlubiIsICJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCnBhcmx2b3RlX2NvcnJfbm9ybTEgPC0gZ2V0X3NlbnRpbWVudChwYXJsdm90ZV9jb3JyLCBjKCJhZmlubiIsICJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCgojIyMgQ09ORklHMgpyZXZpZXdzX2NvbmZpZzJfc2VudGltZW50X25vcm0xIDwtIGdldF9zZW50aW1lbnQocmV2aWV3c19jb25maWcyLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpLCBub3JtYWxpemU9ImFmaW5uIiwgZ2V0X3Rva2Vucz1GQUxTRSkKdHdpdHRlcl9jb25maWcyX3NlbnRpbWVudF9ub3JtMSA8LSBnZXRfc2VudGltZW50KHR3aXR0ZXJfY29uZmlnMiwgYygiYWZpbm4iLCJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCnBhcmx2b3RlX2NvbmZpZzJfc2VudGltZW50X25vcm0xIDwtIGdldF9zZW50aW1lbnQocGFybHZvdGVfY29uZmlnMiwgYygiYWZpbm4iLCJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCmFtYXpvbl9jb25maWcyX3NlbnRpbWVudF9ub3JtMSA8LSBnZXRfc2VudGltZW50KGFtYXpvbl9jb25maWcyLCBjKCJhZmlubiIsICJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9RkFMU0UpCmZpbmFuY2VfY29uZmlnMl9zZW50aW1lbnRfbm9ybTEgPC0gZ2V0X3NlbnRpbWVudChmaW5hbmNlX2NvbmZpZzIsIGMoImFmaW5uIiwgImxzZCIsICJ2YWRlciIpLCBub3JtYWxpemU9ImFmaW5uIiwgZ2V0X3Rva2Vucz1GQUxTRSkKYGBgCgojIyMgMy4gU3RlcCAoT3B0aW9uYWwpOiBDb252ZXJ0IERhdGEgaW50byBkaXNjcmV0ZSBmb3JtYXQKYGBge3J9CiMgQ29udmVydCBmaW5hbCB2YWx1ZXMgaW50byB0ZXJuYXJ5ICgxID0gcG9zaXRpdmUsIDAgPSBuZXV0cmFsLCAtMSA9IG5lZ2F0aXZlKSBmb3JtYXQgZm9yIGV2YWx1YXRpb24gYW5kIGNvbXBhcmlzb24KIyAKIyBBcmc6CiMgICBkZjogaW5wdXQgZGF0YSBmcmFtZSB0aGF0IGNvbnRhaW5zIHRoZSBjb2x1bW5zIHRvIGJlIGNvbnZlcnRlZCBpbnRvIHRlcm5hcnkgZm9ybWF0CiMgICB0b19jaGFuZ2U6IGNvbHVtbiBuYW1lcyB0aGF0IHNob3VsZCBiZSBjb252ZXJ0ZWQgaW50byB0ZXJuYXJ5IGZvcm1hdAojICAgCiMgUmV0dXJuczogCiMgICBkYXRhIGZyYW1lIHdpdGggY29udmVydGVkIHZhbHVlcwoKZ2V0X2Rpc2NyZXRlIDwtIGZ1bmN0aW9uKGRmLCB0b19jaGFuZ2UpewogIGRmICU+JSAKICAgIG11dGF0ZV9hdCh0b19jaGFuZ2UsIGZ1bmN0aW9uKHgpewogICAgICAjIG11dGF0ZSB2YWx1ZXMgZ3JlYXRlciB0aGFuIDAgdG8gMSAocG9zaXRpdmUpLCBlcXVhbCB0byAwIHRvIDAgKG5ldXRyYWwpIGFuZCBzbWFsbGVyIHRoYW4gMCB0byAtMSAobmVnYXRpdmUpCiAgICAgIGNhc2Vfd2hlbih4ID4gMCB+IDEsIHggPCAwIH4gLTEsIHggPT0gMCB+IDApfSkjICU+JSAKfQoKcmV2aWV3c19kaXNjcmV0ZSA8LSBnZXRfZGlzY3JldGUocmV2aWV3c19zZW50aW1lbnRfbm9ybTEsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKdHdpdHRlcl9kaXNjcmV0ZSA8LSBnZXRfZGlzY3JldGUodHdpdHRlcl9zZW50aW1lbnRfbm9ybTEsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKcGFybHZvdGVfZGlzY3JldGUgPC0gZ2V0X2Rpc2NyZXRlKHBhcmx2b3RlX3NlbnRpbWVudF9ub3JtMSwgYygiYWZpbm4iLCJ2YWRlciIsImxzZCIpKQphbWF6b25fZGlzY3JldGUgPC0gZ2V0X2Rpc2NyZXRlKGFtYXpvbl9zZW50aW1lbnRfbm9ybTEsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKZmluYW5jZV9kaXNjcmV0ZSA8LSBnZXRfZGlzY3JldGUoZmluYW5jZV9zZW50aW1lbnRfbm9ybTEsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKYGBgCgojIyMgNC4gU3RlcDogQ29tcHV0ZSBDb3ZlcmFnZQpUbyBjb21wdXRlIHRoZSBjb3ZlcmFnZSBvZiBlYWNoIHNlbnRpbWVudCBsZXhpY29uIGFuZCBmb3IgZWFjaCBjb3JwdXMsIHdlIGNvbnNpZGVyIGFsbCB0ZXh0cyBvciB0b2tlbnMgcmVjZWl2aW5nIGEgc2NvcmUgb2YgMCB0byBiZSBub3QtcmVjb2duaXplZCBieSB0aGUgYWNjb3JkaW5nIGxleGljb24sIGkuZS4gdGhlIHdvcmQgZG9lcyBub3QgZXhpc3QgaW4gdGhlIGxleGljb24gb3IgdGhlIHdob2xlIHRleHQgZGlkIG5vdCBjb250YWluIGEgd29yZCB3aGljaCBpcyBjb250YWluZWQgaW4gdGhlIGxleGljb24uIFdlIGNvbXB1dGUgdGhlIGNvdmVyYWdlIHBlciB0b2tlbiBhbmQgdGhlIGNvdmVyYWdlIHBlciB0ZXh0LiBJbiBjYXNlIG9mIHRoZSBjb3ZlcmFnZSBwZXIgdG9rZW4sIHdlIGNvbnNpZGVyIDIgY29uZmlndXJhdGlvbnM6IDEpIGEgYmFzZWxpbmUgY29uZmlndXJhdGlvbiB3aXRoIGEgc2ltcGxlIHRva2VuLWNvdW50IHRvIGdldCB0aGUgY292ZXJhZ2Ugc2NvcmUsIDIpIGEgY29uZmlndXJhdGlvbiBvZiB0aGUgdG9rZW5zIHdpdGggYSBwcmlvciBzdG9wd29yZCByZW1vdmFsLiAKYGBge3J9CiMgZ2V0IHNlbnRpbWVudCBmb3IgZWFjaCB0b2tlbiBpbiBjb3JwdXMKcmV2aWV3c190b2tfc2VudCA8LSBnZXRfc2VudGltZW50KHJldmlld3MsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIiksIG5vcm1hbGl6ZT0iYWZpbm4iLCBnZXRfdG9rZW5zPVRSVUUpCnR3aXR0ZXJfdG9rX3NlbnQgPC0gZ2V0X3NlbnRpbWVudCh0d2l0dGVyLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpLCBub3JtYWxpemU9ImFmaW5uIiwgZ2V0X3Rva2Vucz1UUlVFKQpwYXJsdm90ZV90b2tfc2VudCA8LSBnZXRfc2VudGltZW50KHBhcmx2b3RlLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpLCBub3JtYWxpemU9ImFmaW5uIiwgZ2V0X3Rva2Vucz1UUlVFKQphbWF6b25fdG9rX3NlbnQgPC0gZ2V0X3NlbnRpbWVudChhbWF6b24sIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIiksIG5vcm1hbGl6ZT0iYWZpbm4iLCBnZXRfdG9rZW5zPVRSVUUpCmZpbmFuY2VfdG9rX3NlbnQgPC0gZ2V0X3NlbnRpbWVudChmaW5hbmNlLCBjKCJhZmlubiIsICJsc2QiLCAidmFkZXIiKSwgbm9ybWFsaXplPSJhZmlubiIsIGdldF90b2tlbnM9VFJVRSkKCgojIGNvbnZlcnQgdmFsdWVzIHRvIGRpc2NyZXRlIGZvcm1hdCAKcmV2aWV3c190b2tfZGlzY3JldGUgPC0gZ2V0X2Rpc2NyZXRlKHJldmlld3NfdG9rX3NlbnQsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKdHdpdHRlcl90b2tfZGlzY3JldGUgPC0gZ2V0X2Rpc2NyZXRlKHR3aXR0ZXJfdG9rX3NlbnQsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKcGFybHZvdGVfdG9rX2Rpc2NyZXRlIDwtIGdldF9kaXNjcmV0ZShwYXJsdm90ZV90b2tfc2VudCwgYygiYWZpbm4iLCJ2YWRlciIsImxzZCIpKQphbWF6b25fdG9rX2Rpc2NyZXRlIDwtIGdldF9kaXNjcmV0ZShhbWF6b25fdG9rX3NlbnQsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKZmluYW5jZV90b2tfZGlzY3JldGUgPC0gZ2V0X2Rpc2NyZXRlKGZpbmFuY2VfdG9rX3NlbnQsIGMoImFmaW5uIiwidmFkZXIiLCJsc2QiKSkKYGBgCgpgYGB7cn0KIyA9PT09PSBDT1ZFUkFHRSBQRVIgVE9LRU4gPT09PT0KCiMgQ292ZXJhZ2UgcGVyIHRva2VuOiBpZiBzZW50aW1lbnQgc2NvcmUgaXMgbm90IHplcm8sIGNvdW50IGFzICJjb3ZlcmVkIiwgZ2V0IHBlcmNlbnRhZ2Ugb2YgY292ZXJlZCB0b2tlbnMKcmV2aWV3c190b2tfY292ZXJhZ2UgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKHJldmlld3NfdG9rX3NlbnRbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS9jb2xTdW1zKHJldmlld3NfdG9rX3NlbnQgIT0gMClbWzFdXSoxMDAsMikpKSwgInJldmlld3MgdG9rZW5zIikKdHdpdHRlcl90b2tfY292ZXJhZ2UgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKHR3aXR0ZXJfdG9rX3NlbnRbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS9jb2xTdW1zKHR3aXR0ZXJfdG9rX3NlbnQgIT0gMClbWzFdXSoxMDAsMikpKSwgInR3aXR0ZXIgdG9rZW5zIikKcGFybHZvdGVfdG9rX2NvdmVyYWdlIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQocm91bmQoY29sU3VtcyhwYXJsdm90ZV90b2tfc2VudFtjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApL2NvbFN1bXMocGFybHZvdGVfdG9rX3NlbnQgIT0gMClbWzFdXSoxMDAsMikpKSwgInBhcmx2b3RlIHRva2VucyIpCmFtYXpvbl90b2tfY292ZXJhZ2UgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKGFtYXpvbl90b2tfc2VudFtjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApL2NvbFN1bXMoYW1hem9uX3Rva19zZW50ICE9IDApW1sxXV0qMTAwLDIpKSksICJhbWF6b24gdG9rZW5zIikKZmluYW5jZV90b2tfY292ZXJhZ2UgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKGZpbmFuY2VfdG9rX3NlbnRbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS9jb2xTdW1zKGZpbmFuY2VfdG9rX3NlbnQgIT0gMClbWzFdXSoxMDAsMikpKSwgImZpbmFuY2UgdG9rZW5zIikKCiMgQ292ZXJhZ2UgcGVyIHRva2VuOiB3aXRoIHN0b3B3b3JkIHJlbW92YWwgCgojIHJlbW92ZSBzdG9wd29yZHMgZnJvbSBlYWNoIGRhdGEgZnJhbWUgCnJldmlld3NfdG9rLnN0b3B3b3JkcyA8LSByZXZpZXdzX3Rva19zZW50ICU+JQogICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnk9IGMoInRva2VuIiA9ICJ3b3JkIikgKSAlPiUKICAgdW5ncm91cCgpCgp0d2l0dGVyX3Rvay5zdG9wd29yZHMgPC0gdHdpdHRlcl90b2tfc2VudCAlPiUKICAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5PSBjKCJ0b2tlbiIgPSAid29yZCIpICkgJT4lCiAgIHVuZ3JvdXAoKQoKcGFybHZvdGVfdG9rLnN0b3B3b3JkcyA8LSBwYXJsdm90ZV90b2tfc2VudCAlPiUKICAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5PSBjKCJ0b2tlbiIgPSAid29yZCIpICkgJT4lCiAgIHVuZ3JvdXAoKQoKYW1hem9uX3Rvay5zdG9wd29yZHMgPC0gYW1hem9uX3Rva19zZW50ICU+JQogICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnk9IGMoInRva2VuIiA9ICJ3b3JkIikgKSAlPiUKICAgdW5ncm91cCgpCgpmaW5hbmNlX3Rvay5zdG9wd29yZHMgPC0gZmluYW5jZV90b2tfc2VudCAlPiUKICAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5PSBjKCJ0b2tlbiIgPSAid29yZCIpICkgJT4lCiAgIHVuZ3JvdXAoKQoKIyBzYW1lIGFzIGFib3ZlOiBpZiBzZW50aW1lbnQgc2NvcmUgaXMgbm90IHplcm8sIGNvdW50IGFzICJjb3ZlcmVkIiwgZ2V0IHBlcmNlbnRhZ2Ugb2YgY292ZXJlZCB0b2tlbnMKcmV2aWV3c190b2tfY292ZXJhZ2Uuc3RvcHdvcmRzIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQocm91bmQoY29sU3VtcyhyZXZpZXdzX3Rvay5zdG9wd29yZHNbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS9jb2xTdW1zKHJldmlld3NfdG9rLnN0b3B3b3JkcyAhPSAwKVtbMV1dKjEwMCwyKSkpLCAicmV2aWV3cyB0b2tlbnMgLSBzdG9wd29yZHMiKQp0d2l0dGVyX3Rva19jb3ZlcmFnZS5zdG9wd29yZHMgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKHR3aXR0ZXJfdG9rLnN0b3B3b3Jkc1tjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApL2NvbFN1bXModHdpdHRlcl90b2suc3RvcHdvcmRzICE9IDApW1sxXV0qMTAwLDIpKSksICJ0d2l0dGVyIHRva2VucyAtIHN0b3B3b3JkcyIpCnBhcmx2b3RlX3Rva19jb3ZlcmFnZS5zdG9wd29yZHMgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKHBhcmx2b3RlX3Rvay5zdG9wd29yZHNbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS9jb2xTdW1zKHBhcmx2b3RlX3Rvay5zdG9wd29yZHMgIT0gMClbWzFdXSoxMDAsMikpKSwgInBhcmx2b3RlIHRva2VucyAtIHN0b3B3b3JkcyIpCmFtYXpvbl90b2tfY292ZXJhZ2Uuc3RvcHdvcmRzIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQocm91bmQoY29sU3VtcyhhbWF6b25fdG9rLnN0b3B3b3Jkc1tjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApL2NvbFN1bXMoYW1hem9uX3Rvay5zdG9wd29yZHMgIT0gMClbWzFdXSoxMDAsMikpKSwgImFtYXpvbiB0b2tlbnMgLSBzdG9wd29yZHMiKQpmaW5hbmNlX3Rva19jb3ZlcmFnZS5zdG9wd29yZHMgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChyb3VuZChjb2xTdW1zKGZpbmFuY2VfdG9rLnN0b3B3b3Jkc1tjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApL2NvbFN1bXMoZmluYW5jZV90b2suc3RvcHdvcmRzICE9IDApW1sxXV0qMTAwLDIpKSksICJmaW5hbmNlIHRva2VucyAtIHN0b3B3b3JkcyIpCgojID09PT09IENPVkVSQUdFIFBFUiBURVhUID09PT09CgojIENvdmVyYWdlIHBlciB0ZXh0OiBpZiBzZW50aW1lbnQgc2NvcmUgaXMgbm90IHplcm8sIGNvdW50IGFzICJjb3ZlcmVkIiwgZ2V0IHBlcmNlbnRhZ2Ugb2YgY292ZXJlZCB0ZXh0IGluc3RhbmNlcwpyZXZpZXdzX2NvdmVyYWdlIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQoY29sU3VtcyhyZXZpZXdzX3NlbnRpbWVudF9ub3JtMVtjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApLzEwKSksICJyZXZpZXdzIHRleHQiKQp0d2l0dGVyX2NvdmVyYWdlIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQoY29sU3Vtcyh0d2l0dGVyX3NlbnRpbWVudF9ub3JtMVtjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApLzEwKSksICJ0d2l0dGVyIHRleHQiKQpwYXJsdm90ZV9jb3ZlcmFnZSA8LSBgcm93bmFtZXM8LWAoZGF0YS5mcmFtZSh0KGNvbFN1bXMocGFybHZvdGVfc2VudGltZW50X25vcm0xW2MoImFmaW5uIiwibHNkIiwidmFkZXIiKV0gIT0gMCkvMTApKSwgInBhcmx2b3RlIHRleHQiKQphbWF6b25fY292ZXJhZ2UgPC0gYHJvd25hbWVzPC1gKGRhdGEuZnJhbWUodChjb2xTdW1zKGFtYXpvbl9zZW50aW1lbnRfbm9ybTFbYygiYWZpbm4iLCJsc2QiLCJ2YWRlciIpXSAhPSAwKS8xMCkpLCAiYW1hem9uIHRleHQiKQpmaW5hbmNlX2NvdmVyYWdlIDwtIGByb3duYW1lczwtYChkYXRhLmZyYW1lKHQoY29sU3VtcyhmaW5hbmNlX3NlbnRpbWVudF9ub3JtMVtjKCJhZmlubiIsImxzZCIsInZhZGVyIildICE9IDApLzEwKSksICJmaW5hbmNlIHRleHQiKQoKIyBzYXZlIGRhdGEgdG8gZGF0YSBmcmFtZSAKY292ZXJhZ2UgPC0gcmJpbmQocmV2aWV3c190b2tfY292ZXJhZ2UscmV2aWV3c190b2tfY292ZXJhZ2Uuc3RvcHdvcmRzLHJldmlld3NfY292ZXJhZ2UsIHR3aXR0ZXJfdG9rX2NvdmVyYWdlLHR3aXR0ZXJfdG9rX2NvdmVyYWdlLnN0b3B3b3JkcyxwYXJsdm90ZV90b2tfY292ZXJhZ2UsdHdpdHRlcl9jb3ZlcmFnZSxwYXJsdm90ZV90b2tfY292ZXJhZ2Uuc3RvcHdvcmRzLCBwYXJsdm90ZV9jb3ZlcmFnZSwgYW1hem9uX3Rva19jb3ZlcmFnZSxhbWF6b25fdG9rX2NvdmVyYWdlLnN0b3B3b3JkcywgYW1hem9uX2NvdmVyYWdlLCBmaW5hbmNlX3Rva19jb3ZlcmFnZSxmaW5hbmNlX3Rva19jb3ZlcmFnZS5zdG9wd29yZHMsIGZpbmFuY2VfY292ZXJhZ2UpCmBgYAoKRGlzcGxheSBDb3ZlcmFnZSBSZXN1bHRzCmBgYHtyfQpjb3ZlcmFnZQpgYGAKCiMjIyA1LiBTdGVwOiBQbG90IERhdGEKIyMjIyA1LjEgUGxvdCBTZW50aW1lbnQgU2NvcmVzCmBgYHtyfQojID09PT09IFBMT1QgU0VOVElNRU5UIFNDT1JFUyA9PT09PQoKIyBjcmVhdGUgZGF0YSBmcmFtZSB3aXRoIHNlbnRpbWVudCBzY29yZXMgYXMgdmFyaWFibGUgb2YgZmlyc3QgMTAwIGluc3RhbmNlcyBvZiBjb3JwdXMKcmV2aWV3c19kZiA8LSBtZWx0KGhlYWQocmV2aWV3c19zZW50aW1lbnRfbm9ybTEsMTAwKVssYygnaWQnLCdhZmlubicsJ2xzZCcsJ3ZhZGVyJyldLGlkLnZhcnMgPSAxKQp0d2l0dGVyX2RmIDwtIG1lbHQoaGVhZCh0d2l0dGVyX3NlbnRpbWVudF9ub3JtMSwxMDApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCnBhcmx2b3RlX2RmIDwtIG1lbHQoaGVhZChwYXJsdm90ZV9zZW50aW1lbnRfbm9ybTEsMTAwKVssYygnaWQnLCdhZmlubicsJ2xzZCcsJ3ZhZGVyJyldLGlkLnZhcnMgPSAxKQphbWF6b25fZGYgPC0gbWVsdChoZWFkKGFtYXpvbl9zZW50aW1lbnRfbm9ybTEsMTAwKVssYygnaWQnLCdhZmlubicsJ2xzZCcsJ3ZhZGVyJyldLGlkLnZhcnMgPSAxKQpmaW5hbmNlX2RmIDwtIG1lbHQoaGVhZChmaW5hbmNlX3NlbnRpbWVudF9ub3JtMSwxMDApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCgojIGNyZWF0ZSBwbG90cyBmb3IgZWFjaCBjb3JwdXMKcmV2aWV3c19wbG90IDwtIGdncGxvdChyZXZpZXdzX2RmLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikgKwogICAgICAgICAgICAgICAgZmFjZXRfd3JhcCh+IHZhcmlhYmxlLCBuY29sID0gMSwgc2NhbGVzPSJmcmVlX3kiKSsKICAgICAgICAgICAgICAgIHhsYWIoInRleHQgaWQiKSsgeWxhYigic2VudGltZW50IHNjb3JlIikrCiAgICAgICAgICAgICAgICBnZ3RpdGxlKCJSZXZpZXdzIFNlbnRpbWVudCIpCgp0d2l0dGVyX3Bsb3QgPC0gZ2dwbG90KHR3aXR0ZXJfZGYsYWVzKHggPSBpZCx5ID0gdmFsdWUpKSArIAogICAgICAgICAgICAgICAgZ2VvbV9iYXIoYWVzKGZpbGwgPSB2YXJpYWJsZSksc3RhdCA9ICJpZGVudGl0eSIscG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgICAgICAgICAgICAgICB4bGFiKCJ0ZXh0IGlkIikrIHlsYWIoInNlbnRpbWVudCBzY29yZSIpKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiVHdpdHRlciBTZW50aW1lbnQiKQoKcGFybHZvdGVfcGxvdCA8LSBnZ3Bsb3QocGFybHZvdGVfZGYsYWVzKHggPSBpZCx5ID0gdmFsdWUpKSArIAogICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikrCiAgICAgICAgICAgICAgICAgI2ZhY2V0X3dyYXAofiB2YXJpYWJsZSwgbmNvbCA9IDEsIHNjYWxlcz0iZnJlZV95IikrCiAgICAgICAgICAgICAgICAgeGxhYigidGV4dCBpZCIpKyB5bGFiKCJzZW50aW1lbnQgc2NvcmUiKSsKICAgICAgICAgICAgICAgICBnZ3RpdGxlKCJQYXJsVm90ZSBTZW50aW1lbnQiKQoKYW1hem9uX3Bsb3QgPC0gZ2dwbG90KGFtYXpvbl9kZixhZXMoeCA9IGlkLHkgPSB2YWx1ZSkpICsgCiAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikrCiAgICAgICAgICAgICAgICNmYWNldF93cmFwKH4gdmFyaWFibGUsIG5jb2wgPSAxLCBzY2FsZXM9ImZyZWVfeSIpKwogICAgICAgICAgICAgICB4bGFiKCJ0ZXh0IGlkIikrIHlsYWIoInNlbnRpbWVudCBzY29yZSIpKwogICAgICAgICAgICAgICBnZ3RpdGxlKCJBbWF6b24gU2VudGltZW50IikKCmZpbmFuY2VfcGxvdCA8LSBnZ3Bsb3QoZmluYW5jZV9kZixhZXMoeCA9IGlkLHkgPSB2YWx1ZSkpICsgCiAgICAgICAgICAgICAgICBnZW9tX2JhcihhZXMoZmlsbCA9IHZhcmlhYmxlKSxzdGF0ID0gImlkZW50aXR5Iixwb3NpdGlvbiA9ICJkb2RnZSIpKwogICAgICAgICAgICAgICAgI2ZhY2V0X3dyYXAofiB2YXJpYWJsZSwgbmNvbCA9IDEsIHNjYWxlcz0iZnJlZV95IikrCiAgICAgICAgICAgICAgICB4bGFiKCJ0ZXh0IGlkIikrIHlsYWIoInNlbnRpbWVudCBzY29yZSIpKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiRmluYW5jZSBTZW50aW1lbnQiKQoKcmV2aWV3c19wbG90LmxpbmUgPC0gZ2dwbG90KHJldmlld3NfZGYsYWVzKHggPSBpZCwgeSA9IHZhbHVlLCBncm91cD12YXJpYWJsZSkpICsKICAgICAgICAgICAgICAgICAgICAgZ2VvbV9saW5lKGFlcyhjb2xvdXI9dmFyaWFibGUpLCBzaXplPTAuNCkrIAogICAgICAgICAgICAgICAgICAgICB5bGltKC0xLDEpICsKICAgICAgICAgICAgICAgICAgICAgZ2d0aXRsZSgiUmV2aWV3cyBTZW50aW1lbnQgU2NvcmVzIikKCiMgc2hvdyBwbG90cwpyZXZpZXdzX3Bsb3QKcmV2aWV3c19wbG90LmxpbmUKdHdpdHRlcl9wbG90CnBhcmx2b3RlX3Bsb3QKYW1hem9uX3Bsb3QKZmluYW5jZV9wbG90IApgYGAKCiMjIyMgNS4yIFBsb3QgSW1wb3J0YW50IFdvcmRzClRvIHBsb3QgaW1wb3J0YW50IHdvcmRzIHBlciBzZW50aW1lbnQgd2UgdXNlIHRoZSBzZW50aW1lbnQgc2NvcmVzIGZvciBlYWNoIHRva2VuICh0aGF0IHdlIGNhbGN1bGF0ZWQgZm9yIHRoZSBjb3ZlcmFnZSBwYXJ0KSBhbmQgY29udmVydCBjb250aW51b3VzIHNjb3JlcyB0byBhIGRpc2NyZXRlIGZvcm1hdC4KYGBge3J9CiMgbG9hZCBzdG9wd29yZHMgCmRhdGEoc3RvcF93b3JkcykKYGBgCgpJbiB0aGUgbmV4dCBzdGVwLCB3b3JkIGNvdW50cyBmb3IgZWFjaCBkaXNjcmV0ZSAoc2VudGltZW50KSB2YXJpYWJsZSAoInBvc2l0aXZlIiwgIm5lZ2F0aXZlIiwgIm5ldXRyYWwiKSBhcmUgcmV0cmlldmVkIGZvciBlYWNoIGxleGljb24uCmBgYHtyfQojIGdldCB3b3JkIGNvdW50cyBwZXIgZGlzY3JldGUgdmFyaWFibGUgKCJwb3NpdGl2ZSIsICJuZWdhdGl2ZSIsICJuZXV0cmFsIiwgaS5lLiAxLC0xLDApCnJldmlld3NfYWZpbm4ud29yZF9jb3VudHMgPC0gcmV2aWV3c190b2tfZGlzY3JldGUgJT4lCiAgIGNvdW50KHRva2VuLCBhZmlubiwgc29ydCA9IFRSVUUpICU+JQogICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnk9IGMoInRva2VuIiA9ICJ3b3JkIikgKSAlPiUKICAgdW5ncm91cCgpCgpyZXZpZXdzX2xzZC53b3JkX2NvdW50cyA8LSByZXZpZXdzX3Rva19kaXNjcmV0ZSAlPiUKICAgY291bnQodG9rZW4sIGxzZCwgc29ydCA9IFRSVUUpICU+JQogICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnk9IGMoInRva2VuIiA9ICJ3b3JkIikgKSAlPiUKICAgdW5ncm91cCgpCgpyZXZpZXdzX3ZhZGVyLndvcmRfY291bnRzIDwtIHJldmlld3NfdG9rX2Rpc2NyZXRlICU+JQogICBjb3VudCh0b2tlbiwgdmFkZXIsIHNvcnQgPSBUUlVFKSAlPiUKICAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5PSBjKCJ0b2tlbiIgPSAid29yZCIpICkgJT4lCiAgIHVuZ3JvdXAoKQpgYGAKCk5vdyB3ZSBjYW4gcGxvdCB0aGUgdG9wIDIwIHdvcmRzIGZvciBlYWNoIHNlbnRpbWVudC4KYGBge3J9CiMgcGxvdCB0b3AgbiB3b3JkcyBmb3IgZWFjaCBzZW50aW1lbnQgYW5kIGZvciBlYWNoIGxleGljb24sIG4gPSAyMAp0b3BuX3Jldmlld3NfYWZpbm4ucGxvdCA8LSByZXZpZXdzX2FmaW5uLndvcmRfY291bnRzICU+JQogICAgICAgICBncm91cF9ieShhZmlubikgJT4lCiAgICAgICAgIHNsaWNlKDE6MjApICU+JQogICAgICAgICBnZ3Bsb3QoYWVzKHJlb3JkZXIodG9rZW4sIG4pLCBuLCBmaWxsID0gYWZpbm4pKSArCiAgICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAgZmFjZXRfd3JhcCh+YWZpbm4sIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgICAgICAgICAgbGFicyh5ID0gImNvbnRyaWJ1dGlvbiB0byBzZW50aW1lbnQiLCB4ID0gTlVMTCkgKwogICAgICAgICAgIGNvb3JkX2ZsaXAoKSsKICAgICAgICAgICBnZ3RpdGxlKCJSZXZpZXdzOiBUb3AgMjAgV29yZHMgcGVyIFNlbnRpbWVudCAoQWZpbm4pIikKIAp0b3BuX3Jldmlld3NfbHNkLnBsb3QgPC0gcmV2aWV3c19sc2Qud29yZF9jb3VudHMgJT4lCiAgICAgICAgIGdyb3VwX2J5KGxzZCkgJT4lCiAgICAgICAgIHNsaWNlKDE6MjApICU+JQogICAgICAgICBnZ3Bsb3QoYWVzKHJlb3JkZXIodG9rZW4sIG4pLCBuLCBmaWxsID0gbHNkKSkgKwogICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgIGZhY2V0X3dyYXAofmxzZCwgc2NhbGVzID0gImZyZWVfeSIpICsKICAgICAgICAgICBsYWJzKHkgPSAiY29udHJpYnV0aW9uIHRvIHNlbnRpbWVudCIsIHggPSBOVUxMKSArCiAgICAgICAgICAgY29vcmRfZmxpcCgpKwogICAgICAgICAgIGdndGl0bGUoIlJldmlld3M6IFRvcCAyMCBXb3JkcyBwZXIgU2VudGltZW50IChMU0QpIikKIAp0b3BuX3Jldmlld3NfdmFkZXIucGxvdCA8LSByZXZpZXdzX3ZhZGVyLndvcmRfY291bnRzICU+JQogICAgICAgICBncm91cF9ieSh2YWRlcikgJT4lCiAgICAgICAgIHNsaWNlKDE6MjApICU+JQogICAgICAgICBnZ3Bsb3QoYWVzKHJlb3JkZXIodG9rZW4sIG4pLCBuLCBmaWxsID0gdmFkZXIpKSArCiAgICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAgZmFjZXRfd3JhcCh+dmFkZXIsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgICAgICAgICAgbGFicyh5ID0gImNvbnRyaWJ1dGlvbiB0byBzZW50aW1lbnQiLCB4ID0gTlVMTCkgKwogICAgICAgICAgIGNvb3JkX2ZsaXAoKSsKICAgICAgICAgICBnZ3RpdGxlKCJSZXZpZXdzOiBUb3AgMjAgV29yZHMgcGVyIFNlbnRpbWVudCAoVkFERVIpIikKCnRvcG5fcmV2aWV3c19hZmlubi5wbG90CnRvcG5fcmV2aWV3c19sc2QucGxvdAp0b3BuX3Jldmlld3NfdmFkZXIucGxvdApgYGAKCgo=